$.extend(CE, {

    ol_img_path: "/media/js/openlayers/img/",

    BBOX: new OpenLayers.Strategy.BBOX(),
    CLUSTER: new OpenLayers.Strategy.Cluster({
        distance: 17
    }),

    LAYERS: {

        get_style_map: function (defaultStyles, attrStyles, attribute) {

            // set the radius of point features to the number of clustered points.
            defaultStyles = $.extend({
                fillColor: "#FFFFFF"
            }, defaultStyles);

            var styleMap = new OpenLayers.StyleMap(
            new OpenLayers.Style(OpenLayers.Util.applyDefaults(
            defaultStyles, OpenLayers.Feature.Vector.style["default"]), {

                // calculate radius of points based on number of clustered points.
                context: {

                    getRadius: function (feature) {
                        if (feature.cluster) {
                            return Math.min(feature.attributes.count, 7) + 2;
                        }
                        return 2;
                    },

                    getFillColor: function (feature) {
                        if (feature.layer.ce.data) {
                            var record = feature.layer.ce.data.fetch({
                                year_index: '' + CE.start_year,
                                spatial_index: feature.attributes.spatial_index
                            });
                            if (!record) {
                                return "#0000ff";
                            }

                            return CE.UTIL.color_fade("#ff0000", Math.log(record[4] + 1) / Math.log(1000)) || "#ffffff";

                        } else {
                            return "#ffffff";
                        }
                    },

                    //Added by Loretta May 2011 to use with Infrastructure Lines and Points layers
                    getColor: function (feature) {
                        var color = feature.attributes.color;
                        switch (color) {
                            case 'red':    return "#FF0000";
                            case 'green':  return "#00FF00";
                            case 'blue':   return "#0000FF";
                            case 'pink':   return "#FF6EB4";
                            case 'orange': return "#FF7F00";
                            case 'rust':   return "#994C16";
                            default:       return "#00FFFF";
                        }
                    },

                    getFillOpacity: function (feature) {
                        if (!feature.layer.ce.data) {
                            return 0;
                        }
                        var record = feature.layer.ce.data.fetch({
                            year_index: '' + CE.start_year,
                            spatial_index: feature.attributes.spatial_index
                        });
                        if (!record) {
                            return 0;
                        }
                        return (Math.log(record[4] + 1) / Math.log(1000));
                    },

                    getLabel: function (feature) {
                        if (feature.layer.ce.data) {
                            var record = feature.layer.ce.data.fetch({
                                year_index: '' + CE.start_year,
                                spatial_index: feature.attributes.spatial_index
                            });
                            if (!record) {
                                return 0;
                            }
                            return record[4];
                        } else {
                            return '';
                        }
                    },

                    csdname: function (feature) {
                        return feature.attributes.csdname;
                    }
                }
            }));

            // map custom style attributes based on gid. (geometry id, see GML response)
            if (attrStyles) {
                styleMap.addUniqueValueRules("default", attribute || "gid", attrStyles);
            }

            return styleMap;

        },

        // Adds custom CE attributes to OpenLayers.Layer instance.
        add_ce_attrs: function (layer, opts) {
            CE.UTIL.default_dates(opts);

            var tags = {};
            if (opts.tag && opts.tag !== 'None') {
                var tags_array = opts.tag.split(',');
                for (var i = 0; i < tags_array.length; i++) {
                    tags[tags_array[i]] = true;
                }
            }

            layer.ce = {
                title: opts.title,
                description: opts.description,
                date_start: opts.date_start,
                date_end: opts.date_end,
                themes: opts.themes,
                show_legend: opts.show_legend === "True",
                tags: tags
            };

            // cache the year for quick access later.
            // it's possible to only define the start year.
            layer.ce.start_year = CE.UTIL.date2year(layer.ce.date_start);
            layer.ce.end_year = CE.UTIL.date2year(layer.ce.date_end) || layer.ce.start_year;
            return layer;
        },
        
        draw_tooltip: function(entity, dom_el){
            CE.MEDIA.infobox = CE.MEDIA.infobox || new CE.WIDGETS.Tooltip({
                items: [
                    {
                        html:'<p>--replace me--</p>'
                    }
                ]
            });
            CE.MEDIA.infobox.update(entity, dom_el);
        },
        
        wfs: {
            // WFS Layer factory.
            gen: function (opts) {

                var layer;

                var style = CE.UTIL.parse_json(opts.style);

                var strategies = [new OpenLayers.Strategy.BBOX()];
                if (opts.cluster == 'True') {
                    strategies.push(new OpenLayers.Strategy.Cluster({
                        distance: 17
                    }));
                }

                CE.filter = new OpenLayers.Filter.Comparison({
                    type: OpenLayers.Filter.Comparison.EQUAL_TO,
                    property: "GID",
                    value: "3"
                });

                layer = new OpenLayers.Layer.Vector(opts.title, {
                    strategies: strategies,
                    visibility:false,
                    protocol: new OpenLayers.Protocol.WFS({
                        url: opts.url + 'map=/usr/local/apps/livingatlas/mapserver/ce_atlas.map',
                        featureType: opts.layers,
                        featurePrefix: "ms",
                        srsName: "ESPG:4326"
                    }),
                    styleMap: CE.LAYERS.get_style_map(style, {}),
                    projection: CE.projectionLatLong,
                    extractAttributes: true,
                    eventListeners: {
                        // when a layer becomes visible, update the legend.
                        visibilitychanged: function (event) {
                            CE.WIDGETS.LEGEND.add(event.object);
                        },
                        loadstart: function (event) {
                            var layer = event.object;
                            if (!layer.data && layer.ce.title == "Waterbird Survey") {
                                CE.LAYERS.load_data(layer);
                            }
                        }
                    }
                });

                layer = CE.LAYERS.add_ce_attrs(layer, opts);
                layer.ce.style = style;
                return layer;
            },
            
            /* Get all the wfs layer definitions from the database */
            load: function () {
                $.getJSON("/get_wfs_layers", function (data) {
                    var sk = CE.UTIL.sorted_keys(data, 'order'),
                        selectable_layers = [];
                   
                    selectable_layers.push(CE.media_layer);
                    selectable_layers.push(CE.user_shape_layer);
                    CE.wfs_layers = [];
                    // loop through each wfs layer
                    for (var i = 0; i < sk.length; i++) {
                        wfs_layer = CE.LAYERS.wfs.gen(data[sk[i]]);

                        // add current wfs layer to map
                        CE.wfs_layers.push(wfs_layer);
                        if (wfs_layer.ce.title !== 'Biosphere Reserve') { //TODO: shouldn't really be hard-coding this
                            wfs_layer.events.on(CE.LAYERS.wfs.select_events);
                            selectable_layers.push(wfs_layer);
                        }
                    }
                    CE.map.addLayers(CE.wfs_layers);

                    CE.controls.select = new OpenLayers.Control.SelectFeature(
                    selectable_layers, {
                        clickout: true,
                        toggle: false,
                        multiple: false,
                        hover: false,
                        toggleKey: "ctrlKey",
                        // ctrl key removes from selection
                        multipleKey: "shiftKey" // shift key adds to selection
                    });
                    
                    // Feature 'info box' uses FLT menus.
                    CE.controls.highlight =  new OpenLayers.Control.SelectFeature(CE.media_layer, {
                        hover: true,
                        highlightOnly: true,
                        renderIntent: "temporary",
                        eventListeners: {
                            featurehighlighted: function(evt){
                                CE.LAYERS.draw_tooltip(evt.feature);
                            },
                            featureunhighlighted: function(f){
                                // do nothing for the time being.
                            }
                        }
                    });
                    
                    // if all the layers have finished loading, initialize the media layer.
                    if (CE.wms_layers) {
                        CE.LAYERS.on_layers_loaded();
                    }
                });
            },

            // TODO refactor "refresh" methods to be observables, or at least have consistent usage.
            // called on time slider update.
            refresh: function () {
                for (var i = 0; i < this.time_indexed_layers.length; i++) {
                    this.time_indexed_layers[i].redraw();
                }
            },

            // click event handlers for wfs layers.
            select_events: {
                "featureselected": function (e) {
                    CE.controls.select.unselect(e.feature);
                    if (e.feature.attributes) {
                        CE.LAYERS.spatial_dialog(e.feature);
                    }
                }
            },

            // create an html table out of wfs feature attribute data.
            render_attribute_data: function (feature) {

                var rows = "",
                    a;
                var attrs = feature.attributes;

                if (attrs.count === 1) {
                    attrs = feature.cluster[0].attributes;
                } else if (attrs.count) {
                    CE.LAYERS.cluster_cache = feature;
                    for (a in feature.cluster) {
                        rows += "<li class='list-item' id='cluster_" + a + "'>gid " + feature.cluster[a].attributes.gid + "</li>";
                    }
                    return "<h3>" + attrs.count + " Items:</h3><ul class='attr-data' >" + rows + "</ul>";
                }

                // render the attribute data.
                for (a in attrs) {
                    rows += "<tr><td>" + a + "</td><td>" + attrs[a] + "</td></tr>";
                }

                // render the dynamic data from the datastore.
                var dyn_data = feature.layer && feature.layer.ce.data && feature.layer.ce.data.fetch_formatted({
                    year_index: '' + CE.start_year,
                    spatial_index: feature.attributes.spatial_index
                });
                if (dyn_data) {
                    for (a in dyn_data) {
                        rows += "<tr><td class='heading'>" + a + "</td><td>" + dyn_data[a] + "</td></tr>";
                    }
                }

                return (feature.layer ? "" : "<img src='/media/img/back.png' class='back'/>") + "<table class='attr-data'>" + rows + "</table>";
            },
            time_indexed_layers: []
        },

        wms: {

            // parameters for the getLegendGraphic request
            legend_params: {
                service: "WMS",
                version: "1.1.1",
                request: "GetLegendGraphic",
                exceptions: "application/vnd.ogc.se_inimage",
                format: "image/png"
            },

            /* Generate a wms layer with a customized field `ce` which contains
             * any meta-information pertaining to the CE atlas. */
            gen: function (opts) {
                layer = new OpenLayers.Layer.WMS(
                opts.title, opts.url, {
                    layers: opts.layers,
                    transparent: CE.formats[opts.format_id].format == 'image/jpg' ? 'false' : 'true',
                    format: CE.formats[opts.format_id].format
                }, {
                    eventListeners: {
                        visibilitychanged: function (layer) {
                            CE.WIDGETS.LEGEND.add(layer.object);
                        }
                    },
                    gutter: 3,
                    // prevents artifacts when loading cascaded layers.
                    visibility: false,
                    isBaseLayer: false,
                    opacity: opts.opacity * 1,
                    singleTile: /\.tif/.test(opts.url)
                });

                layer = CE.LAYERS.add_ce_attrs(layer, opts);

                CE.LAYERS.wms.legend_params.LAYER = opts.layers;

                var query_string = OpenLayers.Util.getParameterString(CE.LAYERS.wms.legend_params);

                layer.ce.legend_url = (opts.connection || opts.url) + query_string;

                return layer;
            },

            load: function () {

                $.getJSON("/get_wms_layers", function (data) {
                    var sk = CE.UTIL.sorted_keys(data, 'order');
                    CE.wms_layers = [];

                    // loop through each wms layer
                    for (var i = 0; i < sk.length; i++) {
                        wms_layer = CE.LAYERS.wms.gen(data[sk[i]]);
                        // add current wms layer to map        
                        CE.wms_layers.push(wms_layer);
                    }
                    CE.map.addLayers(CE.wms_layers);

                    // load media items after so they are on top (actually 
                    // on the bottom of the list, but clickable when WFS and WMS layers are on)
                    if (CE.wfs_layers) {
                        CE.LAYERS.on_layers_loaded();
                    }
                });
            },

            update_time: function (layer) {
                layer.mergeNewParams({
                    'TIME': CE.start_year + '-01-01/' + (CE.start_year + CE.WIDGETS.SLIDER.ruling_interval + 1) + '-12-31'
                });
            }
        },

        georss: {

            load: function () {
                CE.georss_layer = new OpenLayers.Layer.GeoRSS('AMN Users', 'http://nativemaps.org/sites/all/amn_user_georss.php', {
                    projection: CE.projectionLatLong,
                    visibility: false,
                    icon: new OpenLayers.Icon("/media/img/user.png", new OpenLayers.Size(16, 16), new OpenLayers.Pixel(0, 0))
                });
                CE.LAYERS.add_ce_attrs(CE.georss_layer, {
                    title: "AMN Users",
                    description: "AMN user locations.",
                    date_start: 'None',
                    date_end: 'None',
                    themes: [CE.THEME.get_by_title('Aboriginal Mapping Network Users')],
                    show_legend: false,
                    tag: ''
                });
                CE.map.addLayer(CE.georss_layer);
            }
        },

        spatial_dialog: function (feature) {

            var centroid = feature.geometry.getCentroid();

            CE.WIDGETS.DIALOG.draw({

                content: this.wfs.render_attribute_data(feature),
                title: feature.layer ? feature.layer.ce.title + " - " + feature.layer.ce.date_start : 'GID ' + feature.attributes.gid,
                width: 450,
                on_click: function (evt) {
                    var $tgt = $(evt.target);
                    if ($tgt.hasClass('back')) {
                        CE.LAYERS.spatial_dialog(CE.LAYERS.cluster_cache);
                    } else {
                        var id = $tgt.add($tgt.parents()).filter('li').attr('id');
                        if (id) {
                            CE.LAYERS.spatial_dialog(CE.LAYERS.cluster_cache.cluster[
                            id.replace('cluster_', '') * 1]);
                        }
                    }
                }
            });

            var pixel = CE.map.getPixelFromLonLat(
            new OpenLayers.LonLat(centroid.x, centroid.y));

            CE.WIDGETS.DIALOG.stem(pixel.x, pixel.y + CE.$map.offset().top);
        },

        /* CE.layer.load_data()
         * Loads the attribute data for a layer. We load this as an array of arrays, the latter 
         * of which are the records. Once loaded, the data is structured according to a path of 
         * indices used as entry points in the application. */
        load_data: function (layer) {

            $.get("/get_layer_data/waterbird_survey_data", function (rsp) {

                CE.LAYERS.wfs.time_indexed_layers.push(layer);
                layer.ce.data = beget(DataStore).init(CE.UTIL.parse_json(rsp));
                layer.ce.data.filters = {
                    CommonName_index: ''
                };
            });
        },

        // called on theme update.
        // TODO refactor "refresh" methods to be observables, or at least have consistent usage.
        refresh: function () {
            var in_theme, showing;
            // update current layers based on time slider.
            for (var i = 0; i < CE.map.layers.length; i++) {
                layer = CE.map.layers[i];
                // test if the layer is dynamic wrt time.

                if (layer.ce && layer.ce.themes) {
                    // test if layer exists in the current time frame defined by the slider.
                    in_theme = CE.THEME.contains(layer);

                    showing = layer.visibility;
                    // the layer isn't yet displayed but should be...
                    if (!showing && in_theme) {
                        layer.setVisibility(true);
                    }

                    // the layer is displayed but shouldn't be...
                    if (showing && (!in_theme)) {
                        layer.setVisibility(false);
                    }
                    // TODO: possibly add a reference to the layer in tabs panel.
                }
            } // end layer loop
        },

        // returns a layer based on one of its CE custom attributes.
        get_layer_by: function (ce_param, ce_value) {
            for (var i = 0; i < CE.map.layers.length; i++) {
                if (CE.map.layers[i].ce && CE.map.layers[i].ce[ce_param] == ce_value) {
                    return CE.map.layers[i];
                }
            }
        },
		
		get_layer_by_id: function(id) {
			return CE.map.getLayersBy('id',id)[0];
            for (var i = 0; i < CE.map.layers.length; i++) {
                if (CE.map.layers[i] && CE.map.layers[i].id == id) {
                    return CE.map.layers[i];
                }
            }
		},

        on_layers_loaded: function (layer) {
            CE.map.addLayers([CE.media_layer, CE.user_shape_layer]);

            CE.map.addControl(CE.controls.select);
            CE.map.addControl(CE.controls.highlight);

            CE.controls.select.activate();
            CE.controls.highlight.activate();
            
            CE.THEME.set((CE.THEME.pending || CE.THEME.default_theme) + '');

        },

        layer_dialog: function (layer) {

            var content = CE.UTIL.describe(CE.LAYERS.selected_layer.ce),
                data = layer.ce.data,
                val, num, vals_per_col;

            var i = 0;
            if (data) {
                for (var filter in data.filters) {
                    i = 0;
                    vals_per_col = Math.floor(data.indices_by_name[filter].values.length/3) + 1;
                    content += "<div class='index-filter-property'><h3 class='title'>" + filter + "</h3><p>";
                    for (num in data.indices_by_name[filter].values) {
                        val = data.indices_by_name[filter].values[num];
                        i++;
                        if (i % vals_per_col === 0) {
                            content += "</p><p>";
                        }
                        content += "<a href='#' class='index-filter-value" + (data.filters[filter] === val ? ' active' : '') + "'>" + val + "</a><br/>";
                    }
                    content += "</p></div>";
                }
            }

            // create legend meta data dialog...
            CE.WIDGETS.DIALOG.draw({
                title: layer.ce.title,
                content: content,
                // centre dialog on map
                left: $('#ce_map').width() / 2 - 200,
                on_click: function (evt) {
                    $tgt = $(evt.target);
                    if (!$tgt.hasClass('index-filter-value')) {return;}
                    var property = $tgt.parents('.index-filter-property').find('h3.title').text(),
                        value = $tgt.text();

                    // set only the current selection the active class;
                    $tgt.parents('.index-filter-property').find('.active').removeClass('active');
                    $tgt.addClass('active');

                    // update the wfs render style filter.
                    data.filters[property] = value;

                    layer.redraw();
                }
            }).css('top', '100px');
        }
    },

    controls: {

    },

    map_moved: function () {
        if (CE.WIDGETS.DIALOG.$stem) {CE.WIDGETS.DIALOG.$stem.hide();}
        CE.$map_menu.html('[select a map]');

        CE.MEDIA.spatial_refresh();

        var extent = CE.map.getExtent();
        window.location.hash = '#' + Math.floor(extent.left) + '_' + Math.floor(extent.bottom) + '_' + Math.floor(extent.right) + '_' + Math.floor(extent.top) + '_' + (CE.THEME.active_theme ? CE.THEME.active_theme.id : '') + '_' + (CE.start_year || 1970);
    },

    init_map: function () {
                // avoid pink tiles
        OpenLayers.IMAGE_RELOAD_ATTEMPTS = 3;
        OpenLayers.Util.onImageLoadErrorColor = "transparent";

        CE.projectionSphericalMercator = new OpenLayers.Projection("EPSG:900913");
        CE.projectionLatLong = new OpenLayers.Projection("EPSG:4326");

        CE.extent = CE.UTIL.bounds(CE.config.location_western_longitude, CE.config.location_northern_latitude, CE.config.location_eastern_longitude, CE.config.location_southern_latitude);

        // for third party wfs layers, use a proxy:
        OpenLayers.ProxyHost = "/cgi-bin/proxy.cgi?url=";
        // The layer containing
        CE.media_layer = new OpenLayers.Layer.Vector("Media Features");
        CE.user_shape_layer = new OpenLayers.Layer.Vector("User Shape Features");

        CE.media_layer.events.on({
            "featureselected": function (e) {
                if(CE.MEDIA.ADD.$iframe && CE.MEDIA.ADD.$iframe.get(0).contentWindow && $(CE.MEDIA.ADD.$iframe.get(0).contentWindow.document).find('input.existing').attr('checked'))
                {
                    if(''+e.feature.ce.user_shapes_id != 'None' && CE.MEDIA.chooseMode)
                    {
                        CE.MEDIA.chooseMode = false;
                        CE.MEDIA.selectedFeature = e.feature;
                        CE.MEDIA.selectedFeatureFillColor = e.feature.style.fillColor;
                        e.feature.style.fillColor = "#ee00ad";
                        CE.media_layer.redraw();
                        $(CE.MEDIA.ADD.$iframe.get(0).contentWindow.document).find('input.draw').val('UserShapeID:' + e.feature.ce.user_shapes_id);
                        $(CE.MEDIA.ADD.$iframe.get(0).contentWindow.document).find('input[name="latitude"]').val(e.feature.ce.latitude);
                        $(CE.MEDIA.ADD.$iframe.get(0).contentWindow.document).find('input[name="longitude"]').val(e.feature.ce.longitude);
                        $('#ce_dialog').show();
                    }
                }
                else
                {
                    features = [];
                    for (var i = 0; i < CE.MEDIA.features.length; i++)
                    {
                        var feature = CE.MEDIA.features[i];
                        //find all other feature sharing the same user shape as the current selected feature.
                        if (e.feature.id != feature.id && e.feature.ce.user_shapes && feature.ce.user_shapes && e.feature.ce.shape_or_point == feature.ce.shape_or_point && e.feature.ce.user_shapes.id == feature.ce.user_shapes.id)
                            features.push(feature);
                    }
                    if (features.length)
                    {
                        features.push(e.feature);
                        CE.MEDIA.media_list_dialog(features);
                    }
                    else
                        CE.MEDIA.dialog(e.feature);
                }
                CE.controls.select.unselect(e.feature);
            }
        });

        CE.controls.scale_line = new OpenLayers.Control.ScaleLine({
            topOutUnits: "km",
            bottomOutUnits: "",
            topInUnits: "m",
            bottomInUnits: ""
        });

        // GNG 14May10 - added scale text
        CE.controls.scale = new OpenLayers.Control.Scale();

        CE.controls.draw_tool = new OpenLayers.Control.DrawFeature(CE.media_layer, OpenLayers.Handler.Point, {
            handlerOptions: {
                callbacks: {
                    create: function (point) {
                        point.transform(CE.projectionSphericalMercator, CE.projectionLatLong);
                        CE.MEDIA.ADD.set_point(point);
                    }
                }
            }
        });

        var ll2str = function (dec) {
            var pos = Math.abs(dec);
            var deg = Math.floor(pos);
            var min = Math.floor((pos - deg) * 60);
            return deg + '\u00B0' + min + '\u0027' + Math.floor((pos - deg - min / 60) * 3600) + '\u0022';
        };

        function formatLonlats(lonLat) {
            return ll2str(lonLat.lat) + (lonLat.lat > 0 ? ' N' : ' S') + '  ' + ll2str(lonLat.lon) + (lonLat.lon > 0 ? ' E' : ' W');
        }

        var nav = new OpenLayers.Control.NavigationHistory();
        // parent control must be added to the map
        var panel = new OpenLayers.Control.Panel(
        //{div: document.getElementById("panel")}
        );
        panel.addControls([nav.next, nav.previous]);

        var options = {
            projection: CE.projectionSphericalMercator,
            displayProjection: CE.projectionLatLong,
            units: "m",
            eventListeners: {
                //changelayer: mapLayerChanged
            },  
            maxResolution: 156543.0339,
            maxExtent: new OpenLayers.Bounds(-20037508.34, -20037508.34, 20037508.34, 20037508.34),
            controls: [
            new OpenLayers.Control.Navigation(), new OpenLayers.Control.PanZoomBar(), CE.controls.scale, CE.controls.scale_line, new OpenLayers.Control.MousePosition({
                displayProjection: CE.projectionLatLong,
                //id: "ll_mouse",
                formatOutput: formatLonlats
            }), new OpenLayers.Control.CeLayerSwitcher(), new OpenLayers.Control.KeyboardDefaults(), CE.controls.draw_tool, nav, panel]
        };

        CE.map = new OpenLayers.Map('ce_map', options);

        CE.$map_menu = $('#ce_menu_map');

        // define Google layers
        var gmap = new OpenLayers.Layer.Google("Google Streets", {
            'sphericalMercator': true,
            minZoomLevel: 3,
            maxZoomLevel: 19
        });

        var ghyb = new OpenLayers.Layer.Google("Google Hybrid", {
            type: G_HYBRID_MAP,
            'sphericalMercator': true,
            minZoomLevel: 3,
            maxZoomLevel: 19
        });        
    
        if (CE.dev) {

            // only add google hybrid to the map if we're in development
            //CE.map.addLayers([gmap]);
            CE.map.addLayers([ghyb]);

        } else {

            // define Google layers
            // define Virtual Earth/Bing layers
            var vehyb = new OpenLayers.Layer.VirtualEarth("Virtual Earth Hybrid", {
                'type': VEMapStyle.Hybrid,
                'sphericalMercator': true,
                minZoomLevel: 3,
                maxZoomLevel: 19
            });

            var gphy = new OpenLayers.Layer.Google("Google Physical", {
                type: G_PHYSICAL_MAP,
                'sphericalMercator': true,
                minZoomLevel: 3,
                maxZoomLevel: 15
            });

            // define Yahoo layer
            /*var yahoohyb = new OpenLayers.Layer.Yahoo("Yahoo Hybrid", {
             'type': YAHOO_MAP_HYB,
             'sphericalMercator': true
             });*/

            // Virtual Earth Roads commented out for now because the coverage around Clayoquot is poor...
            //var veroad = new OpenLayers.Layer.VirtualEarth("Virtual Earth Roads", {
            //    'type': VEMapStyle.Road,
            //    'sphericalMercator': true
            //});
            // define Open Street Map layer
            // as of March 2012, the following url:
            //var osmarender = new OpenLayers.Layer.OSM("OpenStreetMap", "http://tah.openstreetmap.org/Tiles/tile/${z}/${x}/${y}.png");
            // has been updated to be:
            var osmarender = new OpenLayers.Layer.OSM("OpenStreetMap", "http://c.tile.openstreetmap.org/${z}/${x}/${y}.png");

            // add them all to the map
            //CE.map.addLayers([vehyb, veroad, ghyb, gphy, yahoohyb, osmarender]);
            CE.map.addLayers([ghyb, gphy, osmarender, vehyb]);
        }


        // if extent and theme are specified in URL, apply them.
        if (window.location.hash) {
            var parts = window.location.hash.split("_");
            var e = 1 * parts[0].replace('#', ''),
                s = 1 * parts[1],
                w = 1 * parts[2],
                n = 1 * parts[3];
            var ctr = {
                y: (n + s) / 2,
                x: (e + w) / 2
            };

            // Apparently because of Google Maps' unintuitive zoom level rounding, 
            // zooming to an extent 1/3 smaller than the saved one seems to work well.
            extent = new OpenLayers.Bounds((e * 2 + ctr.x) / 3, (s * 2 + ctr.y) / 3, (w * 2 + ctr.x) / 3, (n * 2 + ctr.y) / 3);
            CE.map.zoomToExtent(extent);
            CE.THEME.pending = 1 * parts[4];
            CE.WIDGETS.SLIDER.pending = 1 * parts[5];
        } else {
            CE.map.zoomToExtent(CE.extent, true);
            CE.THEME.pending = CE.THEME.default_theme;
        }
        CE.map.events.register("moveend", CE.map, this.map_moved);

        $('.olControlCeLayerSwitcher').addClass('layer_switcher_height');
        // line up the checkboxes in the layer switcher pretty.
        $('.olControlCeLayerSwitcher div div:nth(1)').attr('dir', 'rtl');
        $('.olControlCeLayerSwitcher div div:nth(1)').css('height', '' + ($('.olControlCeLayerSwitcher').height() - 140) + 'px');
        $('.olControlCeLayerSwitcher div div:nth(3)').css('height', '80px');

    }
});
